//////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//////////////////////////////////////////////////////////////////////
#include "otpch.h"

#include <string>
#include <fstream>
#include <utility>
#include <cstring>
#include <cerrno>

#include "creature.h"
#include "resources.h"
#include "player.h"
#include "npc.h"
#include "monsters.h"
#include "game.h"
#include "actions.h"
#include "house.h"
#include "iologindata.h"
#include "tools.h"
#include "ban.h"
#include "configmanager.h"
#include "town.h"
#include "spells.h"
#include "talkaction.h"
#include "movement.h"
#include "spells.h"
#include "weapons.h"

#include "raids.h"
#include "chat.h"
#include "quests.h"
#include "mounts.h"
#include "globalevent.h"

#ifdef __ENABLE_SERVER_DIAGNOSTIC__
#include "outputmessage.h"
#include "connection.h"
#include "admin.h"
#include "status.h"
#include "protocollogin.h"
#endif

#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#include "talkaction.h"

extern ConfigManager g_config;
extern Actions* g_actions;
extern Monsters g_monsters;
extern Npcs g_npcs;
extern TalkActions* g_talkActions;
extern MoveEvents* g_moveEvents;
extern Spells* g_spells;
extern Weapons* g_weapons;
extern Game g_game;
extern Chat g_chat;
extern CreatureEvents* g_creatureEvents;
extern GlobalEvents* g_globalEvents;

TalkActions::TalkActions() :
m_scriptInterface("TalkAction Interface")
{
	m_scriptInterface.initState();
}

TalkActions::~TalkActions()
{
	clear();
}

void TalkActions::clear()
{
	for(TalkActionsMap::iterator it = talksMap.begin(); it != talksMap.end(); ++it)
		delete it->second;

	talksMap.clear();
	m_scriptInterface.reInitState();
}

LuaScriptInterface& TalkActions::getScriptInterface()
{
	return m_scriptInterface;
}

std::string TalkActions::getScriptBaseName()
{
	return "talkactions";
}

Event* TalkActions::getEvent(const std::string& nodeName)
{
	if(asLowerCaseString(nodeName) == "talkaction")
		return new TalkAction(&m_scriptInterface);

	return NULL;
}

bool TalkActions::registerEvent(Event* event, xmlNodePtr p)
{
	TalkAction* talkAction = dynamic_cast<TalkAction*>(event);
	if(!talkAction)
		return false;

	talksMap[talkAction->getWords()] = talkAction;
	return true;
}

bool TalkActions::onPlayerSay(Player* player, uint16_t channelId, const std::string& words)
{
	std::string cmdstring[TALKFILTER_LAST] = words, paramstring[TALKFILTER_LAST] = "";
	size_t loc = words.find('"', 0);
	if(loc != std::string::npos && loc >= 0)
	{
		cmdstring[TALKFILTER_QUOTATION] = std::string(words, 0, loc);
		paramstring[TALKFILTER_QUOTATION] = std::string(words, (loc + 1), (words.size() - loc - 1));
		trimString(cmdstring[TALKFILTER_QUOTATION]);
	}

	loc = words.find(" ", 0);
	if(loc != std::string::npos && loc >= 0)
	{
		cmdstring[TALKFILTER_WORD] = std::string(words, 0, loc);
		paramstring[TALKFILTER_WORD] = std::string(words, (loc + 1), (words.size() - loc - 1));
	}

	TalkAction* talkAction = NULL;
	for(TalkActionsMap::iterator it = talksMap.begin(); it != talksMap.end(); ++it)
	{
		if(it->first == cmdstring[it->second->getFilter()] || (!it->second->isSensitive() && 
			strcasecmp(it->first.c_str(), cmdstring[it->second->getFilter()].c_str()) == 0))
		{
			talkAction = it->second;
			break;
		}
	}

	if(!talkAction || (talkAction->getChannel() != -1 && talkAction->getChannel() != channelId))
		return false;

	if(talkAction->getGroup() > player->getGroupId() || talkAction->getAccountType() > player->getAccountType() || player->isAccountManager())
	{
		if(player->isAccessPlayer())
		{
			player->sendTextMessage(MSG_STATUS_SMALL, "You cannot execute this talkaction.");
			return true;
		}

		return false;
	}

	if(talkAction->isLogged())
	{
		player->sendTextMessage(MSG_STATUS_CONSOLE_RED, words.c_str());
		if(dirExists("data/logs/talkactions") || createDir("data/logs/talkactions"))
		{
			std::ostringstream ss;
			ss << "data/logs/talkactions/" << player->getName() << ".log";

			std::ofstream out(ss.str().c_str(), std::ios::app);

			time_t ticks = time(NULL);
			const tm* now = localtime(&ticks);
			char buf[32];
			strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M", now);

			out << "[" << buf << "] " << words << std::endl;

			out.close();
		}
		else
			std::cout << "[Warning - TalkActions::onPlayerSay] Cannot access \"data/logs\" for writing: " << strerror(errno) << "." << std::endl;
	}

	if(talkAction->isScripted())
		return talkAction->executeSay(player, cmdstring[talkAction->getFilter()], paramstring[talkAction->getFilter()], channelId);
	else if(talkAction->function)
		return talkAction->function(player, cmdstring[talkAction->getFilter()], paramstring[talkAction->getFilter()]);

	return false;
}

TalkFunction_t TalkAction::definedFunctions[] =
{
 	{"newtype", newType},
 	{"reloadinfo", reloadInfo},
	{"banplayer", banPlayer},
	{"software", software},
	{"online", online},
	{"sethouseowner", setHouseOwner},
	{"gethouse", getHouse},
	{"forceraid", forceRaid},
	{"addskill", addSkill},
	{"ban", ban},
	{"unban", unban},
	{"ghost", ghost},
	{"mccheck", multiClientCheck},
	{"serverdiag", serverDiag},
	{"buyhouse", buyHouse},
 	{"sellhouse", sellHouse},
	{"serverinfo", serverInfo},
	{"frags", playerKills},
 	{"createguild", createGuild},
 	{"joinguild", joinGuild}
};

TalkAction::TalkAction(LuaScriptInterface* _interface) :
Event(_interface)
{
	m_filter = TALKFILTER_WORD;
	m_groupId = 1;
	m_channel = -1;
	m_logged = false;
	m_sensitive = true;
	m_accountType = ACCOUNT_TYPE_GOD;
}

bool TalkAction::configureEvent(xmlNodePtr p)
{
	std::string strValue;
	if(readXMLString(p, "words", strValue))
		m_words = strValue;
	else
	{
		std::cout << "[Error - TalkAction::configureEvent] No words for TalkAction." << std::endl;
		return false;
	}

	if(readXMLString(p, "filter", strValue))
	{
		std::string tmpStrValue = asLowerCaseString(strValue);
		if(tmpStrValue == "quotation")
			m_filter = TALKFILTER_QUOTATION;
		else if(tmpStrValue == "word")
			m_filter = TALKFILTER_WORD;
		else
			std::cout << "[Warning - TalkAction::configureEvent] Unknown filter for TalkAction: " << strValue << ", using default." << std::endl;
	}

	int32_t intValue;
	if(readXMLInteger(p, "group", intValue) || readXMLInteger(p, "groupid", intValue))
		m_groupId = intValue;

	if(readXMLInteger(p, "acctype", intValue) || readXMLInteger(p, "accounttype", intValue))
		m_accountType = (AccountType_t)intValue;

	if(readXMLInteger(p, "channel", intValue))
		m_channel = intValue;

	if(readXMLString(p, "log", strValue))
		m_logged = booleanString(asLowerCaseString(strValue));

	if(readXMLString(p, "case-sensitive", strValue) || readXMLString(p, "casesensitive", strValue) || readXMLString(p, "sensitive", strValue))
		m_sensitive = booleanString(asLowerCaseString(strValue));

	return true;
}

bool TalkAction::loadFunction(const std::string& functionName)
{
	std::string tmpFunctionName = asLowerCaseString(functionName);
	for(uint32_t i = 0; i < sizeof(definedFunctions) / sizeof(definedFunctions[0]); i++)
	{
		if(tmpFunctionName == definedFunctions[i].name)
		{
			function = definedFunctions[i].callback;
			m_scripted = false;
			return true;
		}
	}

	std::cout << "[Warning - TalkAction::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
	return false;
}

std::string TalkAction::getScriptEventName()
{
	return "onSay";
}

int32_t TalkAction::executeSay(Creature* creature, const std::string& words, const std::string& param, uint16_t channel)
{
	//onSay(cid, words, param, channel)
	if(m_scriptInterface->reserveScriptEnv())
	{
		ScriptEnvironment* env = m_scriptInterface->getScriptEnv();

		#ifdef __DEBUG_LUASCRIPTS__
		std::ostringstream;
		ss << creature->getName() << " - " << words << "- " << param;
		env->setEventDesc(ss.str());
		#endif

		env->setScriptId(m_scriptId, m_scriptInterface);
		env->setRealPos(creature->getPosition());

		uint32_t cid = env->addThing(creature);

		lua_State* L = m_scriptInterface->getLuaState();

		m_scriptInterface->pushFunction(m_scriptId);
		lua_pushnumber(L, cid);
		lua_pushstring(L, words.c_str());
		lua_pushstring(L, param.c_str());
		lua_pushnumber(L, channel);

		bool result = m_scriptInterface->callFunction(4);
		m_scriptInterface->releaseScriptEnv();
		return result;
	}
	else
	{
		std::cout << "[Error - Talkaction::executeSay] Call stack overflow." << std::endl;
		return 0;
	}
}

bool TalkAction::banPlayer(Player* player, const std::string& cmd, const std::string& param)
{
 	std::string name = param;
	Player* playerBan = g_game.getPlayerByName(name);
	if(playerBan)
	{
		if(playerBan->hasFlag(PlayerFlag_CannotBeBanned))
		{
			player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You cannot ban this player.");
			return false;
		}

		playerBan->sendTextMessage(MSG_STATUS_CONSOLE_RED, "You have been banned.");
		uint32_t ip = IOLoginData::getInstance()->getLastIPByName(name);
		if(ip > 0)
			IOBan::getInstance()->addIpBan(ip, 0xFFFFFFFF, (time(NULL) + 86400));

		playerBan->kickPlayer(true);
	}

	return true;
}

bool TalkAction::software(Player* player, const std::string& cmd, const std::string& param)
{
	std::stringstream s;
	s << STATUS_SERVER_NAME << ", version " << STATUS_SERVER_VERSION << "." << std::endl;
	s << "A server developed by: " << STATUS_SERVER_DEVELOPERS << "." << std::endl;
	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, s.str());
	return true;
}

bool TalkAction::reloadInfo(Player* player, const std::string& cmd, const std::string& param)
{
	std::string tmpParam = asLowerCaseString(param);
	if(tmpParam == "action" || tmpParam == "actions")
	{
		g_actions->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded actions.");
	}
	else if(tmpParam == "config" || tmpParam == "configuration")
	{
		g_config.reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded config.");
	}
	else if(tmpParam == "creaturescript" || tmpParam == "creaturescripts")
	{
		g_creatureEvents->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded creature scripts.");
	}
	else if(tmpParam == "highscore" || tmpParam == "highscores")
	{
		g_game.reloadHighscores();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded highscores.");
	}
	else if(tmpParam == "monster" || tmpParam == "monsters")
	{
		g_monsters.reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded monsters.");
	}
	else if(tmpParam == "move" || tmpParam == "movement" || tmpParam == "movements"
		|| tmpParam == "moveevents" || tmpParam == "moveevent")
	{
		g_moveEvents->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded movements.");
	}
	else if(tmpParam == "npc" || tmpParam == "npcs")
	{
		g_npcs.reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded npcs.");
	}
	else if(tmpParam == "raid" || tmpParam == "raids")
	{
		Raids::getInstance()->reload();
		Raids::getInstance()->startup();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded raids.");
	}
	else if(tmpParam == "spell" || tmpParam == "spells")
	{
		g_spells->reload();
		g_monsters.reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded spells.");
	}
	else if(tmpParam == "talk" || tmpParam == "talkaction" || tmpParam == "talkactions")
	{
		g_talkActions->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded talk actions.");
	}
	else if(tmpParam == "items")
	{
		Item::items.reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded items.");
	}
	else if(tmpParam == "weapon" || tmpParam == "weapons")
	{
		g_weapons->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded weapons.");
	}
	else if(tmpParam == "quest" || tmpParam == "quests")
	{
		Quests::getInstance()->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded quests.");
	}
	else if(tmpParam == "mount" || tmpParam == "mounts")
	{
		Mounts::getInstance()->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded mounts.");
	}
	else if(tmpParam == "globalevents" || tmpParam == "globalevent")
	{
		g_globalEvents->reload();
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reloaded globalevents.");
	}
	else
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Reload type not found.");

	return true;
}

bool TalkAction::online(Player* player, const std::string& cmd, const std::string& param)
{
	std::ostringstream ss;
	ss << Player::listPlayer.list.size() << " players online:" << std::endl;
	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());

	ss.str("");

	uint32_t i = 0;
	AutoList<Player>::listiterator it = Player::listPlayer.list.begin();
	if(!g_config.getBoolean(ConfigManager::SHOW_GAMEMASTERS_ONLINE))
	{
		while(it != Player::listPlayer.list.end())
		{
			if(!(*it).second->isAccessPlayer() || player->isAccessPlayer())
			{
				ss << (i > 0 ? ", " : "") << (*it).second->getName() << " [" << (*it).second->getLevel() << "]";
				++i;
			}
			++it;

			if(i == 10)
			{
				ss << (it != Player::listPlayer.list.end() ? "," : ".");
				player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());
				ss.str("");
				i = 0;
			}
		}
	}
	else
	{
		while(it != Player::listPlayer.list.end())
		{
			ss << (i > 0 ? ", " : "") << (*it).second->getName() << " [" << (*it).second->getLevel() << "]";
			++it;
			++i;

			if(i == 10)
			{
				ss << (it != Player::listPlayer.list.end() ? "," : ".");
				player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());
				ss.str("");
				i = 0;
			}
		}
	}

	if(i > 0)
	{
		ss << ".";
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());
	}

	return true;
}

bool TalkAction::setHouseOwner(Player* player, const std::string& cmd, const std::string& param)
{
	if(player->getTile()->hasFlag(TILESTATE_HOUSE))
	{
		HouseTile* houseTile = dynamic_cast<HouseTile*>(player->getTile());
		if(houseTile)
		{
			uint32_t guid;
			std::string name = param;
			if(name == "none")
				houseTile->getHouse()->setHouseOwner(0);
			else if(IOLoginData::getInstance()->getGuidByName(guid, name))
				houseTile->getHouse()->setHouseOwner(guid);
			else
				player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Player not found.");
		}
	}
	
	return true;
}

bool TalkAction::sellHouse(Player* player, const std::string& cmd, const std::string& param)
{
	if(!g_config.getBoolean(ConfigManager::HOUSE_BUY_AND_SELL))
	{
		player->sendCancel("House selling has been disabled by gamemaster.");
		return false;
	}

	House* house = Houses::getInstance().getHouseByPlayerId(player->getGUID());
	if(!house)
	{
		player->sendCancel("You do not own any house.");
		return false;
	}

	Player* tradePartner = g_game.getPlayerByName(param);
	if(!(tradePartner && tradePartner != player))
	{
		player->sendCancel("Trade player not found.");
		return false;
	}

	uint32_t levelToTrade = g_config.getNumber(ConfigManager::LEVEL_TO_BUY_HOUSE);
	if(player->getLevel() < levelToTrade)
	{
		char buffer[90];
		sprintf(buffer, "Trade player level is too low. Him need at least Level %d.", levelToTrade);
		player->sendCancel(buffer);
		return false;
	}

	if(Houses::getInstance().getHouseByPlayerId(tradePartner->getGUID()))
	{
		player->sendCancel("Trade player already owns a house.");
		return false;
	}

	if(!Position::areInRange<2,2,0>(tradePartner->getPosition(), player->getPosition()))
	{
		player->sendCancel("Trade player is too far away.");
		return false;
	}

	if(g_config.getBoolean(ConfigManager::HOUSE_NEED_PREMIUM) && !tradePartner->isPremium())
	{
		player->sendCancel("Trade player does not have a premium account.");
		return false;
	}

	Item* transferItem = house->getTransferItem();
	if(!transferItem)
	{
		player->sendCancel("You can not trade this house.");
		return false;
	}

	transferItem->getParent()->setParent(player);
	if(!g_game.internalStartTrade(player, tradePartner, transferItem))
		house->resetTransferItem();

	return true;
}

bool TalkAction::getHouse(Player* player, const std::string& cmd, const std::string& param)
{
	std::string name = param;
	uint32_t guid;
	if(!IOLoginData::getInstance()->getGuidByName(guid, name))
		return false;

	std::ostringstream str;
	str << name;

	House* house = Houses::getInstance().getHouseByPlayerId(guid);
	if(house)
		str << " owns house: " << house->getName() << ".";
	else
		str << " does not own any house.";

	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, str.str().c_str());

	return true;
}

bool TalkAction::serverInfo(Player* player, const std::string& cmd, const std::string& param)
{
	std::ostringstream text;
	text << "Server Info:";
	text << "\nExp Rate: " << g_game.getExperienceStage(player->getLevel());
	text << "\nSkill Rate: " << g_config.getNumber(ConfigManager::RATE_SKILL);
	text << "\nMagic Rate: " << g_config.getNumber(ConfigManager::RATE_MAGIC);
	text << "\nLoot Rate: " << g_config.getNumber(ConfigManager::RATE_LOOT);
	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, text.str().c_str());

	return true;
}

bool TalkAction::buyHouse(Player* player, const std::string& cmd, const std::string& param)
{
	if(!g_config.getBoolean(ConfigManager::HOUSE_BUY_AND_SELL))
	{
		player->sendCancel("House selling has been disabled by gamemaster.");
		return false;
	}

	if(g_config.getBoolean(ConfigManager::HOUSE_NEED_PREMIUM) && !player->isPremium())
	{
		player->sendCancelMessage(RET_YOUNEEDPREMIUMACCOUNT);
		return false;
	}

	uint32_t levelToBuyHouse = g_config.getNumber(ConfigManager::LEVEL_TO_BUY_HOUSE);
	if(player->getLevel() < levelToBuyHouse)
	{
		char buffer[90];
		sprintf(buffer, "You have to be at least Level %d to purchase a house.", levelToBuyHouse);
		player->sendCancel(buffer);
		return false;
	}

	Position pos = player->getPosition();
	pos = getNextPosition(player->getDirection(), pos);

	Tile* tile = g_game.getTile(pos.x, pos.y, pos.z);
	if(!tile)
	{
		player->sendCancel("You have to be looking at the door of the house you would like to buy.");
		return false;
	}

	HouseTile* houseTile = dynamic_cast<HouseTile*>(tile);
	if(!houseTile)
	{
		player->sendCancel("You have to be looking at the door of the house you would like to buy.");
		return false;
	}

	House* house = houseTile->getHouse();
	if(!house || !house->getDoorByPosition(pos))
	{
		player->sendCancel("You have to be looking at the door of the house you would like to buy.");
		return false;
	}

	if(house->getHouseOwner())
	{
		player->sendCancel("This house alreadly has an owner.");
		return false;
	}

	for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(), end = Houses::getInstance().getHouseEnd(); it != end; ++it)
	{
		if(it->second->getHouseOwner() == player->getGUID())
		{
			player->sendCancel("You are already the owner of a house.");
			return false;
		}
	}

	uint32_t price = 0;
	for(HouseTileList::iterator it = house->getHouseTileBegin(), end = house->getHouseTileEnd(); it != end; ++it)
		price += g_config.getNumber(ConfigManager::HOUSE_PRICE);

	if(g_game.getMoney(player) >= price && g_game.removeMoney(player, price))
	{
		house->setHouseOwner(player->getGUID());
		player->sendTextMessage(MSG_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in your depot of this city.");
	}
	else
		player->sendCancel("You do not have enough money.");

	return true;
}

bool TalkAction::forceRaid(Player* player, const std::string& cmd, const std::string& param)
{
	Raid* raid = Raids::getInstance()->getRaidByName(param);
	if(!raid || !raid->isLoaded())
	{
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "No such raid exists.");
		return false;
	}

	if(Raids::getInstance()->getRunning())
	{
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Another raid is already being executed.");
		return false;
	}

	Raids::getInstance()->setRunning(raid);

	RaidEvent* event = raid->getNextRaidEvent();
	if(!event)
	{
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "The raid does not contain any data.");
		return false;
	}

	raid->setState(RAIDSTATE_EXECUTING);

	uint32_t ticks = event->getDelay();
	if(ticks > 0)
	{
		g_scheduler.addEvent(createSchedulerTask(ticks,
			boost::bind(&Raid::executeRaidEvent, raid, event)));
	}
	else
	{
		g_dispatcher.addTask(createTask(
			boost::bind(&Raid::executeRaidEvent, raid, event)));
	}

	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Raid started.");

	return true;
}

bool TalkAction::addSkill(Player* player, const std::string& cmd, const std::string& param)
{
	boost::char_separator<char> sep(",");
	tokenizer cmdtokens(param, sep);
	tokenizer::iterator cmdit = cmdtokens.begin();
	std::string param1, param2;
	param1 = parseParams(cmdit, cmdtokens.end());
	param2 = parseParams(cmdit, cmdtokens.end());
	trimString(param1);
	trimString(param2);

	Player* paramPlayer = g_game.getPlayerByName(param1);
	if(!paramPlayer)
	{
		player->sendTextMessage(MSG_STATUS_SMALL, "Couldn't find target.");
		return false;
	}

	if(param2[0] == 'l' || param2[0] == 'e')
		paramPlayer->addExperience(Player::getExpForLevel(paramPlayer->getLevel() + 1) - paramPlayer->experience);
	else if(param2[0] == 'm')
		paramPlayer->addManaSpent(player->vocation->getReqMana(paramPlayer->getMagicLevel() + 1) - paramPlayer->manaSpent, false);
	else
		paramPlayer->addSkillAdvance(getSkillId(param2), paramPlayer->vocation->getReqSkillTries(getSkillId(param2), paramPlayer->getSkill(getSkillId(param2), SKILL_LEVEL) + 1));

	return true;
}

bool TalkAction::joinGuild(Player* player, const std::string& cmd, const std::string& param)
{
	if(player->guildId != 0)
	{
		player->sendCancel("You are already in a guild.");
		return false;
	}

	trimString((std::string&)param);
	uint32_t guildId;
	if(!IOGuild::getInstance()->getGuildIdByName(guildId, param))
	{
		player->sendCancel("There's no guild with that name.");
		return false;
	}

	if(!player->isInvitedToGuild(guildId))
	{
		player->sendCancel("You are not invited to that guild.");
		return false;
	}

	player->sendTextMessage(MSG_INFO_DESCR, "You have joined the guild.");
	IOGuild::getInstance()->joinGuild(player, guildId);

	ChatChannel* guildChannel = g_chat.getChannel(player, CHANNEL_GUILD);
	if(guildChannel)
	{
		std::ostringstream ss;
		ss << player->getName() << " has joined the guild.";
		guildChannel->sendToAll(ss.str(), SPEAK_CHANNEL_R1);
	}
	player->setGuildWarList(IOGuild::getInstance()->getWarList(player->getGuildId()));

	return true;
}

bool TalkAction::createGuild(Player* player, const std::string& cmd, const std::string& param)
{
	if(player->guildId != 0)
	{
		player->sendCancel("You are already in a guild.");
		return false;
	}

	trimString((std::string&)param);
	if(param.length() < (uint32_t)g_config.getNumber(ConfigManager::MIN_GUILD_NAME))
	{
		std::ostringstream ss;
		ss << "That guild name is too short, it has to be at least " << g_config.getNumber(ConfigManager::MIN_GUILD_NAME) << " characters.";
		player->sendCancel(ss.str());
		return false;
	}
	else if(param.length() > (uint32_t)g_config.getNumber(ConfigManager::MAX_GUILD_NAME))
	{
		std::ostringstream ss;
		ss << "That guild name is too long, it can not be longer than " << g_config.getNumber(ConfigManager::MAX_GUILD_NAME) << " characters.";
		player->sendCancel(ss.str());
		return false;
	}

	if(!isValidName(param))
	{
		player->sendCancel("Invalid guild name format.");
		return false;
	}

	uint32_t guildId;
	if(IOGuild::getInstance()->getGuildIdByName(guildId, param))
	{
		player->sendCancel("There is already a guild with that name.");
		return false;
	}

	if(player->level < (uint32_t)g_config.getNumber(ConfigManager::LEVEL_TO_CREATE_GUILD))
	{
		std::ostringstream ss;
		ss << "You have to be atleast Level " << g_config.getNumber(ConfigManager::LEVEL_TO_CREATE_GUILD) << " to form a guild.";
		player->sendCancel(ss.str());
		return false;
	}

	if(!player->isPremium())
	{
		player->sendCancelMessage(RET_YOUNEEDPREMIUMACCOUNT);
		return false;
	}

	std::ostringstream ss;
	ss << "You have formed the guild: " << param << "!";
	player->sendTextMessage(MSG_INFO_DESCR, ss.str());
	player->setGuildName(param);

	IOGuild::getInstance()->createGuild(player);

	return true;
}

bool TalkAction::ban(Player* player, const std::string& cmd, const std::string& param)
{
	StringVec exploded = explodeString(param, ", ", 4);
	if(!exploded.size() || exploded.size() < 5)
	{
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Not enough params.");
		return false;
	}

	std::string targetName = exploded[0];

	int32_t action = actionStringToInt(exploded[1]);
	if(action == -1)
		action = (int32_t)atoi(exploded[1].c_str());

	int32_t reason = reasonStringToInt(exploded[2]);
	if(reason == -1)
		reason = (int32_t)atoi(exploded[2].c_str());

	bool ipBan = (atoi(exploded[3].c_str()) != 0);

	std::string comment = exploded[4];
	g_game.violationWindow(player, targetName, reason, action, comment, ipBan);

	return true;
}

bool TalkAction::unban(Player* player, const std::string& cmd, const std::string& param)
{
	uint32_t accountNumber = atoi(param.c_str());
	bool removedIPBan = false;
	std::string name = param;
	bool playerExists = false;
	if(IOLoginData::getInstance()->playerExists(name))
	{
		playerExists = true;
		accountNumber = IOLoginData::getInstance()->getAccountNumberByName(name);

		uint32_t lastIP = IOLoginData::getInstance()->getLastIPByName(name);
		if(lastIP != 0 && IOBan::getInstance()->isIpBanished(lastIP))
			removedIPBan = IOBan::getInstance()->removeIPBan(lastIP);
	}

	bool banned = false;
	bool deleted = false;
	uint32_t bannedBy = 0, banTime = 0;
	int32_t reason = 0, action = 0;
	std::string comment = "";
	if(IOBan::getInstance()->getBanInformation(accountNumber, bannedBy, banTime, reason, action, comment, deleted))
	{
		if(!deleted)
			banned = true;
	}

	if(banned)
	{
		if(IOBan::getInstance()->removeAccountBan(accountNumber))
		{
			std::ostringstream ss;
			ss << name << " has been unbanned.";
			player->sendTextMessage(MSG_INFO_DESCR, ss.str());
		}
	}
	else if(deleted)
	{
		if(IOBan::getInstance()->removeAccountDeletion(accountNumber))
		{
			std::ostringstream ss;
			ss << name << " has been undeleted.";
			player->sendTextMessage(MSG_INFO_DESCR, ss.str());
		}
	}
	else if(removedIPBan)
	{
		std::ostringstream ss;
		ss << "The IP banishment on " << name << " has been lifted.";
		player->sendTextMessage(MSG_INFO_DESCR, ss.str());
	}
	else
	{
		bool removedNamelock = false;
		if(playerExists)
		{
			uint32_t guid = 0;
			if(IOLoginData::getInstance()->getGuidByName(guid, name) &&
				IOBan::getInstance()->isPlayerNamelocked(name) &&
				IOBan::getInstance()->removePlayerNamelock(guid))
			{
				std::ostringstream ss;
				ss << "Namelock on " << name << " has been lifted.";
				player->sendTextMessage(MSG_INFO_DESCR, ss.str());
				removedNamelock = true;
			}
		}

		if(!removedNamelock)
			player->sendCancel("That player or account is not banished or deleted.");
	}

	return true;
}

bool TalkAction::playerKills(Player* player, const std::string& cmd, const std::string& param)
{
	int32_t fragTime = g_config.getNumber(ConfigManager::FRAG_TIME);
	if(player->skullTicks && fragTime > 0)
	{
		int32_t frags = (int32_t)ceil(player->skullTicks / (double)fragTime);
		int32_t remainingTime = (player->skullTicks % fragTime) / 1000;
		int32_t hours = remainingTime / 3600;
		int32_t minutes = (remainingTime % 3600) / 60;

		std::ostringstream ss;
		ss << "You have " << frags << " unjustified kill" << (frags > 1 ? "s" : "") << ". The amount of unjustified kills will decrease after: " << hours << " hour" << (hours != 1 ? "s" : "") << " and " << minutes << " minute" << (minutes != 1 ? "s" : "") << ".";
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());
	}
	else
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "You do not have any unjustified frag.");

	return true;
}

bool TalkAction::serverDiag(Player* player, const std::string& cmd, const std::string& param)
{
#ifdef __ENABLE_SERVER_DIAGNOSTIC__
	std::ostringstream text;
	text << "Server diagonostic:\n";
	text << "World:" << "\n";
	text << "Player: " << g_game.getPlayersOnline() << " (" << Player::playerCount << ")\n";
	text << "Npc: " << g_game.getNpcsOnline() << " (" << Npc::npcCount << ")\n";
	text << "Monster: " << g_game.getMonstersOnline() << " (" << Monster::monsterCount << ")\n";

	text << "\nProtocols:" << "\n";
	text << "--------------------\n";
	text << "ProtocolGame: " << ProtocolGame::protocolGameCount << "\n";
	text << "ProtocolLogin: " << ProtocolLogin::protocolLoginCount << "\n";
	text << "ProtocolAdmin: " << ProtocolAdmin::protocolAdminCount << "\n";
	text << "ProtocolStatus: " << ProtocolStatus::protocolStatusCount << "\n\n";

	text << "\nConnections:\n";
	text << "--------------------\n";
	text << "Active connections: " << Connection::connectionCount << "\n";
	text << "Total message pool: " << OutputMessagePool::getInstance()->getTotalMessageCount() << "\n";
	text << "Auto message pool: " << OutputMessagePool::getInstance()->getAutoMessageCount() << "\n";
	text << "Free message pool: " << OutputMessagePool::getInstance()->getAvailableMessageCount() << "\n";

	text << "\nLibraries:\n";
	text << "--------------------\n";
	text << "asio: " << BOOST_ASIO_VERSION << "\n";
	text << "libxml: " << XML_DEFAULT_VERSION << "\n";
	text << "lua: " << LUA_VERSION << "\n";

	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, text.str().c_str());
#else
	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "This command requires the server to be compiled with the __ENABLE_SERVER_DIAGNOSTIC__ flag.");
#endif

	return true;
}

bool TalkAction::ghost(Player* player, const std::string& cmd, const std::string& param)
{
	player->switchGhostMode();
	Player* tmpPlayer;

	SpectatorVec list;
	g_game.getSpectators(list, player->getPosition(), true);

	SpectatorVec::const_iterator it;
	for(it = list.begin(); it != list.end(); ++it)
	{
		if((tmpPlayer = (*it)->getPlayer()))
		{
			tmpPlayer->sendCreatureChangeVisible(player, !player->isInGhostMode());
			if(tmpPlayer != player && !tmpPlayer->isAccessPlayer())
			{
				if(player->isInGhostMode())
					tmpPlayer->sendCreatureDisappear(player, player->getTile()->getClientIndexOfThing(tmpPlayer, player), true);
				else
					tmpPlayer->sendCreatureAppear(player, player->getPosition(), true);

				tmpPlayer->sendUpdateTile(player->getTile(), player->getPosition());
			}
		}
	}

	for(it = list.begin(); it != list.end(); ++it)
		(*it)->onUpdateTile(player->getTile(), player->getPosition());

	if(player->isInGhostMode())
	{
		for(AutoList<Player>::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it)
		{
			if(!it->second->isAccessPlayer())
				it->second->notifyLogOut(player);
		}

		IOLoginData::getInstance()->updateOnlineStatus(player->getGUID(), false);
		player->sendTextMessage(MSG_INFO_DESCR, "You are now invisible.");
		g_game.addMagicEffect(player->getPosition(), NM_ME_YALAHARIGHOST);
	}
	else
	{
		for(AutoList<Player>::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it)
		{
			if(!it->second->isAccessPlayer())
				it->second->notifyLogIn(player);
		}

		IOLoginData::getInstance()->updateOnlineStatus(player->getGUID(), true);
		player->sendTextMessage(MSG_INFO_DESCR, "You are visible again.");
		Position pos = player->getPosition();
		pos.x += 1;
		g_game.addMagicEffect(pos, NM_ME_SMOKE);
	}

	return true;
}

bool TalkAction::multiClientCheck(Player* player, const std::string& cmd, const std::string& param)
{
	std::list<uint32_t> ipList;

	player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, "Multiclient Check List:");
	std::map< uint32_t, std::vector<Player*> > ipMap;
	for(AutoList<Player>::listiterator it = Player::listPlayer.list.begin(); it != Player::listPlayer.list.end(); ++it)
	{
		if(it->second->isRemoved() || it->second->getIP() == 0)
			continue;

		ipMap[it->second->getIP()].push_back(it->second);
	}

	for(std::map< uint32_t, std::vector<Player*> >::const_iterator it = ipMap.begin(), end = ipMap.end(); it != end; ++it)
	{
		if(it->second.size() < 2)
			continue;

		Player* tmpPlayer = it->second[0];
		std::ostringstream ss;
		ss << convertIPToString(it->first) << ": " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]";
		for(std::vector<Player*>::size_type i = 1, size = it->second.size(); i < size; ++i)
		{
			tmpPlayer = it->second[i];
			ss << ", " << tmpPlayer->getName() << " [" << tmpPlayer->getLevel() << "]";
		}
		ss << ".";
		player->sendTextMessage(MSG_STATUS_CONSOLE_BLUE, ss.str());
	}

	return true;
}

bool TalkAction::newType(Player* player, const std::string& cmd, const std::string& param)
{
	int32_t lookType = atoi(param.c_str());
	if(lookType >= 0 && lookType != 1 && lookType != 135 && lookType != 411 && lookType != 415 && lookType != 424 && (lookType <= 160 || lookType >= 192) && lookType != 439 && lookType != 440 && lookType != 468 && lookType != 469 && lookType <= 517 && (lookType < 474 || lookType > 485))
	{
		Outfit_t newOutfit = player->getDefaultOutfit();
		newOutfit.lookType = lookType;
		g_game.internalCreatureChangeOutfit(player, newOutfit);
	}
	else
		player->sendTextMessage(MSG_STATUS_SMALL, "This looktype does not exist.");

	return true;
}
